#include <windows.h>
#include "iasiodrv.h"
#include "asiolist.h"

#define ASIODRV_DESC "description"
#define INPROC_SERVER "InprocServer32"
#define ASIO_PATH "software\\asio"
#define COM_CLSID "clsid"

// ******************************************************************
// Local Functions
// ******************************************************************
static LONG findDrvPath(char* clsidstr, char* dllpath, int dllpathsize)
{
    HKEY hkEnum, hksub, hkpath;
    char databuf[512];
    LONG cr, rc = -1;
    DWORD datatype, datasize;
    DWORD index;
    OFSTRUCT ofs;
    HFILE hfile;
    BOOL found = FALSE;

    #ifdef UNICODE
    CharLowerBuffA(clsidstr, strlen(clsidstr));
    if ((cr = RegOpenKeyA(HKEY_CLASSES_ROOT, COM_CLSID, &hkEnum)) == ERROR_SUCCESS)
    {

        index = 0;
        while (cr == ERROR_SUCCESS && !found)
        {
            cr = RegEnumKeyA(hkEnum, index++, databuf, 512);
            if (cr == ERROR_SUCCESS)
            {
                CharLowerBuffA(databuf, strlen(databuf));
                if (!(strcmp(databuf, clsidstr)))
                {
                    if ((cr = RegOpenKeyExA(hkEnum, databuf, 0, KEY_READ, &hksub)) == ERROR_SUCCESS)
                    {
                        if ((cr = RegOpenKeyExA(hksub, INPROC_SERVER, 0, KEY_READ, &hkpath)) == ERROR_SUCCESS)
                        {
                            datatype = REG_SZ; datasize = (DWORD)dllpathsize;
                            cr = RegQueryValueEx(hkpath, 0, 0, &datatype, (LPBYTE)dllpath, &datasize);
                            if (cr == ERROR_SUCCESS)
                            {
                                memset(&ofs, 0, sizeof(OFSTRUCT));
                                ofs.cBytes = sizeof(OFSTRUCT);
                                hfile = OpenFile(dllpath, &ofs, OF_EXIST);
                                if (hfile) rc = 0;
                            }
                            RegCloseKey(hkpath);
                        }
                        RegCloseKey(hksub);
                    }
                    found = TRUE;   // break out
                }
            }
        }
        RegCloseKey(hkEnum);
    }
    #else
    CharLowerBuff(clsidstr, strlen(clsidstr));
    if ((cr = RegOpenKey(HKEY_CLASSES_ROOT, COM_CLSID, &hkEnum)) == ERROR_SUCCESS)
    {

        index = 0;
        while (cr == ERROR_SUCCESS && !found)
        {
            cr = RegEnumKey(hkEnum, index++, databuf, 512);
            if (cr == ERROR_SUCCESS)
            {
                CharLowerBuff(databuf, strlen(databuf));
                if (!(strcmp(databuf, clsidstr)))
                {
                    if ((cr = RegOpenKeyEx(hkEnum, databuf, 0, KEY_READ, &hksub)) == ERROR_SUCCESS)
                    {
                        if ((cr = RegOpenKeyEx(hksub, INPROC_SERVER, 0, KEY_READ, &hkpath)) == ERROR_SUCCESS)
                        {
                            datatype = REG_SZ; datasize = (DWORD)dllpathsize;
                            cr = RegQueryValueEx(hkpath, 0, 0, &datatype, (LPBYTE)dllpath, &datasize);
                            if (cr == ERROR_SUCCESS)
                            {
                                memset(&ofs, 0, sizeof(OFSTRUCT));
                                ofs.cBytes = sizeof(OFSTRUCT);
                                hfile = OpenFile(dllpath, &ofs, OF_EXIST);
                                if (hfile) rc = 0;
                            }
                            RegCloseKey(hkpath);
                        }
                        RegCloseKey(hksub);
                    }
                    found = TRUE;   // break out
                }
            }
        }
        RegCloseKey(hkEnum);
    }
    #endif
    return rc;
}


static LPASIODRVSTRUCT newDrvStruct(HKEY hkey, char* keyname, int drvID, LPASIODRVSTRUCT lpdrv)
{
    HKEY hksub;
    char databuf[256];
    char dllpath[MAXPATHLEN];
    WORD wData[100];
    CLSID clsid;
    DWORD datatype, datasize;
    LONG cr, rc;

    if (!lpdrv)
    {
        if ((cr = RegOpenKeyExA(hkey, keyname, 0, KEY_READ, &hksub)) == ERROR_SUCCESS)
        {

            datatype = REG_SZ; datasize = 256;
            cr = RegQueryValueExA(hksub, COM_CLSID, 0, &datatype, (LPBYTE)databuf, &datasize);
            if (cr == ERROR_SUCCESS)
            {
                rc = findDrvPath(databuf, dllpath, MAXPATHLEN);
                if (rc == 0)
                {
                    lpdrv = new ASIODRVSTRUCT[1];
                    if (lpdrv)
                    {
                        memset(lpdrv, 0, sizeof(ASIODRVSTRUCT));
                        lpdrv->drvID = drvID;
                        MultiByteToWideChar(CP_ACP, 0, (LPCSTR)databuf, -1, (LPWSTR)wData, 100);
                        if ((cr = CLSIDFromString((LPOLESTR)wData, (LPCLSID)&clsid)) == S_OK)
                        {
                            memcpy(&lpdrv->clsid, &clsid, sizeof(CLSID));
                        }

                        datatype = REG_SZ; datasize = 256;
                        cr = RegQueryValueExA(hksub, ASIODRV_DESC, 0, &datatype, (LPBYTE)databuf, &datasize);
                        if (cr == ERROR_SUCCESS)
                        {
                            strcpy(lpdrv->drvname, databuf);
                        }
                        else strcpy(lpdrv->drvname, keyname);
                    }
                }
            }
            RegCloseKey(hksub);
        }
    }
    else lpdrv->next = newDrvStruct(hkey, keyname, drvID + 1, lpdrv->next);

    return lpdrv;
}

static void deleteDrvStruct(LPASIODRVSTRUCT lpdrv)
{
    IASIO* iasio;

    if (lpdrv != 0)
    {
        deleteDrvStruct(lpdrv->next);
        if (lpdrv->asiodrv)
        {
            iasio = (IASIO*)lpdrv->asiodrv;
            iasio->Release();
        }
        delete lpdrv;
    }
}


static LPASIODRVSTRUCT getDrvStruct(int drvID, LPASIODRVSTRUCT lpdrv)
{
    while (lpdrv)
    {
        if (lpdrv->drvID == drvID) return lpdrv;
        lpdrv = lpdrv->next;
    }
    return 0;
}
// ******************************************************************


// ******************************************************************
//	AsioDriverList
// ******************************************************************
AsioDriverList::AsioDriverList ()
{
    HKEY hkEnum = 0;
    char keyname[MAXDRVNAMELEN];
    LPASIODRVSTRUCT pdl;
    LONG cr;
    DWORD index = 0;
    BOOL fin = FALSE;

    numdrv = 0;
    lpdrvlist = 0;

    #ifdef UNICODE
    cr = RegOpenKeyA(HKEY_LOCAL_MACHINE, ASIO_PATH, &hkEnum);
    #else
    cr = RegOpenKey(HKEY_LOCAL_MACHINE, ASIO_PATH, &hkEnum);
    #endif
    while (cr == ERROR_SUCCESS)
    {
        #ifdef UNICODE
        if ((cr = RegEnumKeyA(hkEnum, index++, keyname, MAXDRVNAMELEN)) == ERROR_SUCCESS)
        {
        #else
        if ((cr = RegEnumKey(hkEnum, index++, keyname, MAXDRVNAMELEN)) == ERROR_SUCCESS)
        {
            #endif
            lpdrvlist = newDrvStruct(hkEnum, keyname, 0, lpdrvlist);
        }
        else fin = TRUE;
    }
    if (hkEnum) RegCloseKey(hkEnum);

    pdl = lpdrvlist;
    while (pdl)
    {
        numdrv++;
        pdl = pdl->next;
    }

    if (numdrv) CoInitialize(0);    // initialize COM
}

AsioDriverList::~AsioDriverList ()
{
    if (numdrv)
    {
        deleteDrvStruct(lpdrvlist);
        CoUninitialize();
    }
}


LONG AsioDriverList::asioGetNumDev(VOID)
{
    return (LONG)numdrv;
}


LONG AsioDriverList::asioOpenDriver(int drvID, LPVOID* asiodrv)
{
    LPASIODRVSTRUCT lpdrv = 0;
    long rc;

    if (!asiodrv) return DRVERR_INVALID_PARAM;

    if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0)
    {
        if (!lpdrv->asiodrv)
        {
            rc = CoCreateInstance(lpdrv->clsid, 0, CLSCTX_INPROC_SERVER, lpdrv->clsid, asiodrv);
            if (rc == S_OK)
            {
                lpdrv->asiodrv = *asiodrv;
                return 0;
            }
            // else if (rc == REGDB_E_CLASSNOTREG)
            //	strcpy (info->messageText, "Driver not registered in the Registration Database!");
        }
        else rc = DRVERR_DEVICE_ALREADY_OPEN;
    }
    else rc = DRVERR_DEVICE_NOT_FOUND;

    return rc;
}


LONG AsioDriverList::asioCloseDriver(int drvID)
{
    LPASIODRVSTRUCT lpdrv = 0;
    IASIO* iasio;

    if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0)
    {
        if (lpdrv->asiodrv)
        {
            iasio = (IASIO*)lpdrv->asiodrv;
            iasio->Release();
            lpdrv->asiodrv = 0;
        }
    }

    return 0;
}

LONG AsioDriverList::asioGetDriverName(int drvID, char* drvname, int drvnamesize)
{
    LPASIODRVSTRUCT lpdrv = 0;

    if (!drvname) return DRVERR_INVALID_PARAM;

    if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0)
    {
        if (strlen(lpdrv->drvname) < (unsigned int)drvnamesize)
        {
            strcpy(drvname, lpdrv->drvname);
        }
        else
        {
            memcpy(drvname, lpdrv->drvname, drvnamesize - 4);
            drvname[drvnamesize - 4] = '.';
            drvname[drvnamesize - 3] = '.';
            drvname[drvnamesize - 2] = '.';
            drvname[drvnamesize - 1] = 0;
        }
        return 0;
    }
    return DRVERR_DEVICE_NOT_FOUND;
}

LONG AsioDriverList::asioGetDriverPath(int drvID, char* dllpath, int dllpathsize)
{
    LPASIODRVSTRUCT lpdrv = 0;

    if (!dllpath) return DRVERR_INVALID_PARAM;

    if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0)
    {
        if (strlen(lpdrv->dllpath) < (unsigned int)dllpathsize)
        {
            strcpy(dllpath, lpdrv->dllpath);
            return 0;
        }
        dllpath[0] = 0;
        return DRVERR_INVALID_PARAM;
    }
    return DRVERR_DEVICE_NOT_FOUND;
}

LONG AsioDriverList::asioGetDriverCLSID(int drvID, CLSID* clsid)
{
    LPASIODRVSTRUCT lpdrv = 0;

    if (!clsid) return DRVERR_INVALID_PARAM;

    if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0)
    {
        memcpy(clsid, &lpdrv->clsid, sizeof(CLSID));
        return 0;
    }
    return DRVERR_DEVICE_NOT_FOUND;
}